local gotDailies = gotDailies

-- Function to figure out which action a macro is going to.
local line
local function GetMacroAction(macrobody, last)
	if not macrobody then
		return
	end
	macrobody = macrobody:gsub("\\n", "\n")
	local result, action, type, target
	local pos
	local init = 1
	local newline = macrobody:find("\n")
	repeat
		line = macrobody:sub(init, newline and newline -1)
		-- Check if line contains an action.
		if line:find("^/cast") or line:find("^/use") or line:find("#show") then
			-- Evaluate the conditionals.
			result, target = SecureCmdOptionParse(line)
			if result then
				result = result:gsub("/%a+ ","")
				result = result:gsub("#show ", "")
				-- Check if we're dealing with a castsequence.
				if line:find("/%a+sequence") then
					local _,item,spell = QueryCastSequence(result)
					
					if item then
						action = item
						type = "item"
					else
						action = spell
						type = "spell"
					end
				else
					action = result
					type = GetSpellInfo(result) and "spell" or "item"
				end
				
				-- Return first action unless last is given, in which case the last action is returned.
				if not last then
					break
				end
			end
		end
		init = newline + 1
		newline = line:find("\n", init)
		pos = macrobody:find("\n", pos)
	until not pos

	return action, type, target
end

-- Button control
local buttons = {}	-- Button pile
gotDailies.buttons = buttons

local Button = CreateFrame("Button")
function Button:UpdateIcon()
	local tex 
	if not self.data.nextaction then
		tex = GetItemIcon(self.data.action)
	else
		-- We're dealing with a macro.
		if self.data.type == "spell" then
			tex = GetSpellTexture(self.data.nextaction)
		elseif self.data.type == "item" then
			tex = GetItemIcon(self.data.nextaction)
		else
			tex = GetItemIcon(self.data.nextaction)
			if tex then
				self.data.type = "item"
			else
				tex = GetSpellTexture(self.data.nextaction)
				self.data.type = tex and "spell"
			end
		end
	end

	-- If for whatever reason the texture is nil, use the question mark.
	if not tex then
		tex = [[Interface\Icons\INV_Misc_QuestionMark]]
	end

	self.icon:SetTexture(tex)
end

function Button:UpdateCount()
	if not self.data.type or self.data.type == "item" then
		local item = self.data.nextaction or self.data.action
		if IsConsumableItem(item) then
			return self.count:SetText(GetItemCount(item))
		end
	end
	self.count:SetText("")
end

function Button:UpdateCooldown()
	if self.data.type == "spell" then
		CooldownFrame_SetTimer(self.cooldown, GetSpellCooldown(self.data.nextaction or self.data.action))
	else
		CooldownFrame_SetTimer(self.cooldown, GetItemCooldown(self.data.nextaction or self.data.action))
	end
end

function Button:UpdateUsable()
	local usable, nomana
	if self.data.type == "spell" then
		usable, nomana = IsUsableSpell(self.data.nextaction)
	else
		usable = IsUsableItem(self.data.nextaction or self.data.action)
	end
	
	if self.inrange == 0 then
		self.icon:SetVertexColor(1.0, 0, 0)
	elseif usable then
		self.icon:SetVertexColor(1.0,1.0,1.0)
	elseif nomana then
		self.icon:SetVertexColor(0.5, 0.5, 1.0)
	else
		self.icon:SetVertexColor(0.4,0.4,0.4)
	end
end

function Button:UpdateMacroAction()
	if self.data.macro then
		local lastaction = self.data.nextaction
		self.data.nextaction, self.data.type, self.data.target = GetMacroAction(self:GetAttribute("macrotext"))
		if self.data.nextaction ~= lastaction then
			self:UpdateIcon()
			self:UpdateCount()
			self:UpdateCooldown()
		end
	end
end

function Button:Update()
	self:UpdateMacroAction()
	self:UpdateIcon()
	self:UpdateCount()
	self:UpdateCooldown()
end

function Button:Clear()
	if InCombatLockdown() then
		gotDailies:AddDebugLine("Clear command given in combat; rescheduling.")
		return gotDailies:ScheduleForLeavingCombat(self, "Clear")
	end
	
	for k,v in pairs(self.data) do
		self.data[k] = nil
	end
	
	self:SetAttribute("type", nil)
	
	CooldownFrame_SetTimer(self.cooldown, 0,0,0)
	
	self.icon:SetTexture(nil)
	self:UpdateCount()

	if gotDailies.settings.hideempty then
		self:Hide()
		gotDailies:AddDebugLine("Button:Clear: Button cleared and hidden.")
	end
	
	gotDailies:UpdateButtons()
	if gotDailies.settings.priofirst and not buttons[1]:IsShown() then
		if buttons[2]:IsShown() then
			buttons[2]:MoveToPosition(1)
		end
	end
end

function Button:MoveToPosition(pos)
	if InCombatLockdown() then
		return gotDailies:ScheduleForLeavingCombat(self, "MoveToFirst", pos)
	end
	
	local target = buttons[pos]
	if not target then
		error("Invalid target button: invalid number: "..tostring(pos))
	end
	
	local actiontype = self:GetAttribute("type")
	target:SetAttribute("type", actiontype)
	if actiontype == "macro" then
		target:SetAttribute("macrotext", self:GetAttribute("macrotext"))
	elseif actiontype == "item" then
		target:SetAttribute("item", self:GetAttribute("item"))
	end
	for k,v in pairs(self.data) do
		target.data[k] = v
	end
	
	self:Clear()
	
	target:Show()
end

--Widget Handler functions
local function OnEvent(self, event, ...)
	if event == "BAG_UPDATE" then
		-- Filter the somewhat excessive BAG_UPDATES
		if ... >= 0 and not self.data.type or self.data.type == "item" then
			self:UpdateCount()
		end
	elseif event == "BAG_UPDATE_COOLDOWN" or event == "SPELL_UPDATE_COOLDOWN" then
		self:UpdateCooldown()
	elseif event == "MODIFIER_STATE_CHANGED" then
		self:UpdateMacroAction()
	end
end

local IsItemInRange = IsItemInRange
local function OnUpdate(self, elapsed)

	self.interval = self.interval - elapsed
	if self.interval <= 0 then
		self:UpdateUsable()
		self:UpdateMacroAction()
		self.interval = 0.2
	end
	
	if self.rangecheck then
		self.rangecheck = self.rangecheck - elapsed
		if self.rangecheck <= 0 then
			if self.data.type == "spell" then
				self.inrange = IsSpellInRange(self.data.nextaction)
			else
				self.inrage = IsItemInRange(self.data.nextaction or self.data.action)
			end
			self.rangecheck = 0.2
		end
	end
end

local function OnDragStart(self)
	if InCombatLockdown() then return end
	
	if IsShiftKeyDown() then
		self:StartMoving()
	end
end

local function OnDragStop(self)
	self:StopMovingOrSizing()
end

local function OnShow(self)
	self:Update()
end

local function OnEnter(self)
	if gotDailies.settings.tooltip then
		local link, _
		if self.data.macro and self.data.nextaction then
			if self.data.type == "spell" then
				local pos = self.data.nextaction:find(".%(")
				local spell = self.data.nextaction:sub(0, pos)
				if pos then
					local rank = self.data.nextaction:sub(pos+2, #self.data.nextaction-1)
				end
				link = GetSpellLink(spell, rank)
			else
				_, link = GetItemInfo(self.data.nextaction)
			end
		elseif self.data.action then
			_, link = GetItemInfo(self.data.action)
		end
		if link then
			GameTooltip:SetOwner(self, "ANCHOR_LEFT")
			GameTooltip:SetHyperlink(link)
		end
	end
end

local function OnLeave(self)
	if GameTooltip:IsOwned(self) then
		GameTooltip:Hide()
	end
end

local ButtonMT = {__index = Button}
function gotDailies:CreateButton()
	local id = #buttons + 1
	local name = "gotDailiesButton"..id
	local button = setmetatable(CreateFrame("Button", name, UIParent, "SecureActionButtonTemplate, ActionButtonTemplate"), ButtonMT)
	
	button.name = _G[name.."Name"]
	button.name:SetText(id)
	button.name:Hide()
	button.data = {}

	-- Set up default behaviour.
	button:SetPoint("CENTER")
	button:RegisterForClicks("AnyUp")
	button:SetMovable(true)
	button:SetUserPlaced(true)
	button:RegisterForDrag("RightButton")
	
	if self.settings.hideempty then
		button:Hide()
	end
	
	button:SetScript("OnDragStart", OnDragStart)
	button:SetScript("OnDragStop", OnDragStop)
	button:SetScript("OnUpdate", OnUpdate)
	button:SetScript("OnEvent", OnEvent)
	button:SetScript("OnShow", OnShow)
	button:SetScript("OnEnter", OnEnter)
	button:SetScript("OnLeave", OnLeave)
	
	button:RegisterEvent("BAG_UPDATE")
	button:RegisterEvent("BAG_UPDATE_COOLDOWN")
	button:RegisterEvent("SPELL_UPDATE_COOLDOWN")
	button:RegisterEvent("MODIFIER_STATE_CHANGED")
	
	button.interval = 0.2	-- Generic OnUpdate interval
	button.rangecheck = self.settings.rangecheck and 0.2
	
	button.id = id
	button.icon = _G[name.."Icon"]
	button.count = _G[name.."Count"]
	button.cooldown = _G[name.."Cooldown"]
	button.normalTexture = _G[name.."NormalTexture"]
	
	-- Throw the button on the pile.
	buttons[#buttons + 1] = button
	
	if gotDailies.LBFgroup then
		gotDailies.LBFgroup:AddButton(button, button.LBFData)
	end
	
	return button
end

function gotDailies:SetButton(questid, questtable)
	local zoneMajor, zoneMinor = self.zoneMajor, self.zoneMinor
	-- If we're in combat at the moment, schedule the update for leaving combat.
	if InCombatLockdown() then
		self:AddDebugLine("SetButton: Attempt to setup button in combat for questID %d, table: %s. Rescheduling.", questid, tostring(questtable))
		return self:ScheduleForLeavingCombat(self, "SetButton", questid, questtable)
	end
	
	-- Find an available button we can use, be it not set or set to a different zone.
	local button
	for _,btn in ipairs(buttons) do
		if btn.data.questid == questid then
			-- A button has already been set up for this quest. 
			-- Stop the loop and return this button so it can be updated.
			button = btn
			self:AddDebugLine("SetButton: Button already found for questID %d, updating.", questid)
			break
		end
		
		-- Find a button that isn't used for this zone.
		if btn.data.zoneMajor ~= zoneMajor or btn.data.zoneMinor ~= zoneMinor then
			button = button or btn
		end
	end
	
	if not button then
		-- Not enough buttons, create a new one until we reach the limit.
		if #buttons < 2 then
			button = self:CreateButton()
		else
			self:AddDebugLine("SetButton: No available button for questID %d", questid)
			return
		end
	end
	
	button.data = {
		questid = questid,
		action = questtable.action,
		zoneMajor = zoneMajor,
		zoneMinor = zoneMinor,
	}
	
	local action, actiontype = questtable.action
	if type(action) == "table" then
		local entry
		for i=1, #action do
			if type(action) ~= "table" then
				break
			end
			entry = action[i]
			assert(type(entry) == "table", string.format("Bad value #%d to questtable.action for quest %d: table expected, got %q", i, questid, type(entry)))
			for i=1,#entry-1 do
				if entry[i] == zoneMinor then
					action = entry[#entry]
					button.data.action = action
					break
				end
			end
		end
	end
	if tonumber(action) then
		actiontype = "item"
	else
		actiontype = "macro"
	end

	button:SetAttribute("type", actiontype)
	if actiontype == "item" then
		button:SetAttribute("item", "item:"..action)
	else
		button:SetAttribute("macrotext", action:gsub("\\n", "\n"))
		button.data.macro = true
	end
	
	if self.settings.rangecheck then
		button.rangecheck = 0
	end
	
	button:Show()
	button:Update()
end